package aceim.protocol.snuk182.mrim.inner; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import aceim.protocol.snuk182.mrim.inner.dataentity.MrimMessage; import aceim.protocol.snuk182.mrim.inner.dataentity.MrimOnlineInfo; import aceim.protocol.snuk182.mrim.inner.dataentity.MrimPacket; import aceim.protocol.snuk182.mrim.utils.ProtocolUtils; public final class MrimServiceInternal { public static final short STATE_DISCONNECTED = 0; public static final short STATE_CONNECTING_LOGIN = 1; public static final short STATE_CONNECTING_BOS = 2; public static final short STATE_AUTHENTICATING = 3; public static final short STATE_CONNECTED = 4; public static final short REQ_NOP = 0; public static final short REQ_CONNECT = 1; public static final short REQ_DISCONNECT = 2; public static final short REQ_SETSTATUS = 3; public static final short REQ_SETEXTENDEDSTATUS = 4; public static final short REQ_GETSTATUS = 5; public static final short REQ_GETEXTENDEDSTATUS = 6; public static final short REQ_SENDMESSAGE = 7; public static final short REQ_SENDFILE = 8; public static final short REQ_GETSHORTBUDDYINFO = 9; public static final short REQ_GETOWNINFO = 10; public static final short REQ_SETOWNINFO = 11; public static final short REQ_ADDBUDDY = 12; public static final short REQ_REMOVEBUDDY = 13; public static final short REQ_EDITCONTACTLIST = 14; public static final short REQ_SEARCHFORBUDDY_BY_UID = 15; public static final short REQ_SAVEPARAMS = 16; public static final short REQ_GETCONTACTLIST = 17; public static final short REQ_GETGROUPLIST = 18; public static final short REQ_GETICON = 19; public static final short REQ_AUTHREQUEST = 20; public static final short REQ_AUTHRESPONSE = 21; public static final short REQ_RENAMEBUDDY = 22; public static final short REQ_MOVEBUDDY = 23; public static final short REQ_RENAMEGROUP = 24; public static final short REQ_ADDGROUP = 25; public static final short REQ_REMOVEGROUP = 26; public static final short REQ_MOVEBUDDIES = 27; public static final short REQ_REMOVEBUDDIES = 28; public static final short REQ_FILERESPOND = 29; public static final short REQ_FILECANCEL = 30; public static final short REQ_GETFULLBUDDYINFO = 31; public static final short REQ_SENDTYPING = 32; private String loginHost = "mrim.mail.ru"; private int loginPort = 2042; private String mrid; private String pw; //private byte[] internalIp = new byte[]{0,0,0,0}; private MrimServiceResponse serviceResponse; private MrimRunnableService runnableService; private final List<MrimPacket> packets = Collections.synchronizedList(new ArrayList<MrimPacket>()); MrimProcessor processor = new MrimProcessor(this); private short currentState = STATE_DISCONNECTED; //private int currentStatus = MrimConstants.STATUS_ONLINE; private final MrimOnlineInfo onlineInfo = new MrimOnlineInfo(); public String lastConnectionError = null; private long pingFrequency = 120; private FileTransferEngine fileTransferEngine; public MrimServiceInternal(){ onlineInfo.status = MrimConstants.STATUS_ONLINE; onlineInfo.xstatusId = "STATUS_ONLINE"; onlineInfo.xstatusName = "x-name"; onlineInfo.xstatusText = "x-text"; } public MrimServiceInternal(MrimServiceResponse response){ this.serviceResponse = response; } public void runService(String host, int port){ if (runnableService!=null){ runnableService.connected = false; } runnableService = new MrimRunnableService(host, port); runnableService.start(); } public class MrimRunnableService extends Thread{ private Socket socket; private String host; private int port; private volatile boolean connected = true; private int flapSeqNumber = 0; public int getFlapSeqNumber() { if (flapSeqNumber >=0xffffffff){ flapSeqNumber = 0; }; return flapSeqNumber++; } public MrimRunnableService(String host, int port){ this.host = host; this.port = port; setName("MRIM runnable "+mrid+" "+host); } public void runService(String host, int port){ if (runnableService!=null){ runnableService.connected = false; } runnableService = new MrimRunnableService(host, port); runnableService.start(); } protected void forcePacketProcess() throws Exception{ while(packets.size()>0){ synchronized (packets) { MrimPacket packet = packets.remove(0); processor.parsePacketTail(packet); } } } @Override public void run() { flapSeqNumber = ProtocolUtils.getAtomicShort(); try { socket = new Socket(); //socket.setSoTimeout(300000); socket.connect(new InetSocketAddress(InetAddress.getByName(host), port)); connected = true; if (!host.equals(loginHost)){ processor.sendHello(); } getDataFromSocket(); } catch (UnknownHostException e) { serviceResponse.respond(MrimServiceResponse.RES_NOTIFICATION, "host not found!"); new Timer().schedule(new ErrorTimer(), 5000); log(e); } catch (IOException e) { serviceResponse.respond(MrimServiceResponse.RES_NOTIFICATION, "connection error!"); new Timer().schedule(new ErrorTimer(), 5000); log(e); } } private void getDataFromSocket(){ byte[] tail = null; int read = 0; int tailLength = 0; while (connected && socket!=null && socket.isConnected() && !socket.isClosed()){ try { InputStream is = socket.getInputStream(); if (is.available()>0){ Thread.sleep(200); byte[] head = new byte[44]; read = is.read(head, 0, 44); if (currentState != STATE_CONNECTING_LOGIN){ tailLength = (int) ProtocolUtils.unsignedInt2Long(ProtocolUtils.bytes2IntLE(head, 16)); tail = new byte[44+tailLength]; System.arraycopy(head, 0, tail, 0, 44); read = 0; while (read < tailLength){ read = is.read(tail, 44+read, tailLength-read); } log("Got "+ProtocolUtils.getSpacedHexString(tail)); } else { // omit newline char at the end tail = new byte[read-1]; log("Got "+ProtocolUtils.getSpacedHexString(tail)); System.arraycopy(head, 0, tail, 0, read-1); } try { MrimPacket packet = processor.parsePacket(tail); synchronized(packets){ packets.add(packet); } } catch (Exception e) { log(e); } new Thread("MRIM packet processor"){ @Override public void run(){ try { forcePacketProcess(); } catch (Exception e) { log(e); } } }.start(); tail = null; } else { Thread.sleep(1000); } }catch(IOException e){ log(e); new Thread("mrim disconnection"){ @Override public void run(){ disconnect(); } }.start(); }catch (Exception e) { log(e); } } log(getName()+" disconnected"); connected = false; } public synchronized long sendToSocket(MrimPacket packet){ try { OutputStream os = socket.getOutputStream(); packet.seqNumber = getFlapSeqNumber(); byte[] out = processor.packet2Bytes(packet); if (currentState != STATE_AUTHENTICATING){ log("To be sent "+ProtocolUtils.getSpacedHexString(out)); } else { log("smth secret to be sent"); } //log("To be sent "+ProtocolUtils.getSpacedHexString(out)); os.write(out); return packet.seqNumber; //checkForKeepaliveTimer(); } catch (NullPointerException e) { log(e); } catch (IOException e) { log(e); disconnect(); } catch (MrimException e) { log(e); } return 0; } public String getHost() { return host; } public void setHost(String host) { this.host = host; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public void disconnect(){ log("attempt disconnect "+currentState+ ((lastConnectionError!= null) ? lastConnectionError : "")); //closeKeepaliveTimerThread(); if (socket!=null && !socket.isClosed()){ try { socket.close(); connected = false; } catch (IOException e) { log(e); } } /*if (fileTransferEngine != null){ fileTransferEngine.cancelAll(); }*/ if (currentState != STATE_CONNECTING_BOS){ if (lastConnectionError != null){ serviceResponse.respond(MrimServiceResponse.RES_DISCONNECTED, lastConnectionError); lastConnectionError = null; } else { serviceResponse.respond(MrimServiceResponse.RES_DISCONNECTED); } setCurrentState(STATE_DISCONNECTED); } } class ErrorTimer extends TimerTask{ @Override public void run() { disconnect(); } } } public void log(String string) { serviceResponse.respond(MrimServiceResponse.RES_LOG, string); } public void log(Exception e) { StringBuilder sb = new StringBuilder(); sb.append(e.toString()); for (StackTraceElement el:e.getStackTrace()){ sb.append("\n"+el); } log(sb.toString()); } public short getCurrentState() { return currentState; } public void setCurrentState(short currentState) { this.currentState = currentState; } public MrimRunnableService getRunnableService() { return runnableService; } public MrimServiceResponse getServiceResponse() { return serviceResponse; } @SuppressWarnings("unchecked") public Object request(short action, final Object... args) throws MrimException { switch(action){ case REQ_CONNECT: mrid = (String) args[0]; pw = (String) args[1]; if (args[2] !=null){ loginHost = (String) args[2]; } if (args[3] != null){ loginPort = Integer.parseInt(((String) args[3]).trim().replace("\n", "")); } onlineInfo.status = (Integer) args[4]; onlineInfo.xstatusId = "STATUS_ONLINE"; //TODO edit onlineInfo.xstatusName = (String) args[6]!=null ? (String) args[6] : ""; onlineInfo.xstatusText = (String) args[7]!=null ? (String) args[7] : ""; connectInternal(); break; case REQ_DISCONNECT: log("disconnect direct request"); closeKeepalive(); if (runnableService != null){ runnableService.disconnect(); } break; case REQ_SENDMESSAGE: processor.sendMessage((MrimMessage)args[0]); break; case REQ_GETICON: processor.getIcon((String)args[0]); break; case REQ_SETSTATUS: onlineInfo.status = (Integer) args[0]; onlineInfo.xstatusId = (String) args[1]; onlineInfo.xstatusName = (String) args[2]; onlineInfo.xstatusText = (String) args[3]; processor.setStatus(onlineInfo); break; case REQ_SENDTYPING: processor.sendTyping((String)args[0]); break; case REQ_SENDFILE: final String buddyMrid = (String) args[0]; final List<File> files = (List<File>) args[1]; return getFileTransferEngine().sendFiles(buddyMrid, files, runnableService.socket.getLocalAddress().getAddress(), (Integer)args[2]); case REQ_FILERESPOND: final byte[] intIp; if (args.length > 2){ intIp = (byte[]) args[2]; } else { intIp = new byte[]{127,0,0,1}; } new Thread("File accept response"){ @Override public void run(){ getFileTransferEngine().fileReceiveResponse((Long)args[0], (Boolean)args[1], intIp); } }.start(); break; } return null; } public void connectInternal() { setCurrentState(STATE_CONNECTING_LOGIN); serviceResponse.respond(MrimServiceResponse.RES_CONNECTING, 1); runService(loginHost, loginPort); } public FileTransferEngine getFileTransferEngine() { if (fileTransferEngine == null){ fileTransferEngine = new FileTransferEngine(this); } return fileTransferEngine; } public String getLoginHost() { return loginHost; } public int getLoginPort() { return loginPort; } public String getMrid() { return mrid; } public String getPw() { return pw; } public void setPingFrequency(long pingFreq) { this.pingFrequency = pingFreq; } public long getPingFrequency() { return pingFrequency; } //keepalive timer, required for mrim :(:( private ScheduledFuture<?> task; private ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(4); private Runnable keepaliveRunnable = new Runnable(){ @Override public void run() { processor.sendKeepalive(); startKeepalive(); } }; public void closeKeepalive(){ if (task != null){ task.cancel(false); } } public void startKeepalive(){ task = executor.schedule(keepaliveRunnable, pingFrequency , TimeUnit.SECONDS); } public MrimOnlineInfo getOnlineInfo() { return onlineInfo; } public void askForWebAuthKey() { processor.askForWebAuthKey(); } }